﻿using System;
using System.Management;
using Nexus.IPConfiguration.Management.Wrappers;
using Microsoft.Win32;

namespace Nexus.IPConfiguration.Management
{
    /// <summary>
    /// Encapsulated Windows network adapter
    /// </summary>
    public class NetworkAdapter : IDisposable
    {
        #region fields
        private ManagementObject adapter;
        private bool isDisposed = false;
        #endregion

        #region constants
        private const int INTERNET_OPTION_SETTINGS_CHANGED = 39;
        private const int INTERNET_OPTION_REFRESH = 37;
        #endregion

        #region properties
        /// <summary>
        /// Caption
        /// </summary>
        public string Caption
        {
            get
            {
                return adapter["Caption"] as string;
            }
        }

        /// <summary>
        /// Default gateway
        /// </summary>
        public string[] DefaultIPGateway
        {
            get
            {
                return adapter["DefaultIPGateway"] as string[];
            }
        }

        /// <summary>
        /// Description
        /// </summary>
        public string Description
        {
            get
            {
                return adapter["Description"] as string;
            }
        }

        /// <summary>
        /// Indicates if DHCP is enabled
        /// </summary>
        public bool IsDHCPEnabled
        {
            get
            {
                return (bool)adapter["DHCPEnabled"];
            }
        }

        /// <summary>
        /// DHCP server address
        /// </summary>
        public string DHCPServer
        {
            get
            {
                return adapter["DHCPServer"] as string;
            }
        }

        /// <summary>
        /// DNS domain
        /// </summary>
        public string DNSDomain
        {
            get
            {
                return adapter["DNSDomain"] as string;
            }
        }

        /// <summary>
        /// DNS host name
        /// </summary>
        public string DNSHostName
        {
            get
            {
                return adapter["DNSHostName"] as string;
            }
        }

        /// <summary>
        /// DNS servers
        /// </summary>
        public string[] DNSServers
        {
            get
            {
                return adapter["DNSServerSearchOrder"] as string[];
            }
        }

        /// <summary>
        /// IP address
        /// </summary>
        public string[] IPAddress
        {
            get
            {
                return adapter["IPAddress"] as string[];
            }
        }

        /// <summary>
        /// Indicates if this newtowrk adapter support IP configuration
        /// </summary>
        public bool IsIPEnabled
        {
            get
            {
                return (bool)adapter["IPEnabled"];
            }
        }

        /// <summary>
        /// Subnet mask
        /// </summary>
        public string[] SubnetMask
        {
            get
            {
                return adapter["IPSubnet"] as string[];
            }
        }

        /// <summary>
        /// Setting ID
        /// </summary>
        public string SettingID
        {
            get
            {
                return adapter["SettingID"] as string;
            }
        }

        /// <summary>
        /// Indicates if this network adapter gets DNS servers automatically
        /// </summary>
        public bool ObtainDNSAutomatically
        {
            get
            {
                string path = string.Format(@"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{" + SettingID + @"}");
                using (RegistryKey key = Registry.LocalMachine.OpenSubKey(path))
                {
                    string nameServer = key.GetValue("NameServer") as string;
                    return string.IsNullOrWhiteSpace(nameServer);
                }
            }
        }

        ///// <summary>
        ///// Gets list of network adapter DNS servers
        ///// </summary>
        //public string[] NameServers
        //{
        //    get
        //    {
        //        string path = string.Format(@"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{" + SettingID + @"}\NameServer");
        //        using (RegistryKey key = Registry.LocalMachine.OpenSubKey(path))
        //        {
        //            string nameServer = key.GetValue("NameServer") as string;
        //            if (string.IsNullOrWhiteSpace(nameServer))
        //            {
        //                return null;
        //            }
        //            else
        //            {
        //                string[] nameServers = nameServer.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
        //                for (int i = 0; i < nameServers.Length; i++)
        //                {
        //                    nameServers[i] = nameServers[i].Trim();
        //                }
        //                return nameServers;
        //            }
        //        }
        //    }
        //}
        #endregion

        #region methods

        private void LoadNetworkAdapter(string settingID, bool ipOnly)
        {
            if (string.IsNullOrWhiteSpace(settingID))
            {
                throw new ArgumentNullException("settingID");
            }
            ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
            foreach (ManagementObject instance in mc.GetInstances())
            {
                if ((instance["SettingID"] as string) == settingID)
                {
                    if ((bool)instance["ipEnabled"] == false && ipOnly)
                    {
                        throw new ArgumentException("Requested network adapter does not allow IP configuration");
                    }
                    adapter = instance;
                    break;
                }
            }
            if (adapter == null)
            {
                throw new ArgumentException("Cannot find network adapter with given caption");
            }
            mc.Dispose();
        }

        /// <summary>
        /// Refreshes data
        /// </summary>
        public void Refresh()
        {
            if (adapter != null)
            {
                LoadNetworkAdapter(SettingID, IsIPEnabled);
            }
        }

        /// <summary>
        /// Enabled DHCP on this network adapter
        /// </summary>
        public void EnableDHCP()
        {
            if (isDisposed)
            {
                throw new ObjectDisposedException("NetworkAdapter");
            }
            ManagementBaseObject enableDHCPResult = adapter.InvokeMethod("EnableDHCP", null, null);
            enableDHCPResult.Dispose();
        }

        /// <summary>
        /// Enabled auto obtaining DNS servers
        /// </summary>
        public void EnableAutoDNS()
        {
            if (isDisposed)
            {
                throw new ObjectDisposedException("NetworkAdapter");
            }
            ManagementBaseObject autoDns = adapter.GetMethodParameters("SetDNSServerSearchOrder");
            ManagementBaseObject autoDnsResult = adapter.InvokeMethod("SetDNSServerSearchOrder", autoDns, null);
            autoDns.Dispose();
            autoDnsResult.Dispose();
        }

        /// <summary>
        /// Sets DNS servers
        /// </summary>
        /// <param name="dns">List of two DNS servers addresses' (primary and secondary)</param>
        public void SetDns(string[] dns)
        {
            if (isDisposed)
            {
                throw new ObjectDisposedException("NetworkAdapter");
            }
            if (dns == null)
            {
                throw new ArgumentNullException("dns");
            }
            ManagementBaseObject newDns = adapter.GetMethodParameters("SetDNSServerSearchOrder");
            newDns["DNSServerSearchOrder"] = dns;
            ManagementBaseObject setDns = adapter.InvokeMethod("SetDNSServerSearchOrder", newDns, null);
            newDns.Dispose();
            setDns.Dispose();
        }

        /// <summary>
        /// Sets DNS servers
        /// </summary>
        /// <param name="dns">DNS servers addresses</param>
        public void SetDns(DNS dns)
        {
            if (isDisposed)
            {
                throw new ObjectDisposedException("NetworkAdapter");
            }
            if (dns == null)
            {
                throw new ArgumentNullException("dns");
            }
            ManagementBaseObject newDns = adapter.GetMethodParameters("SetDNSServerSearchOrder");
            newDns["DNSServerSearchOrder"] = new string[] { dns.DNSAddress1, dns.DNSAddress2 };
            ManagementBaseObject setDns = adapter.InvokeMethod("SetDNSServerSearchOrder", newDns, null);
            newDns.Dispose();
            setDns.Dispose();
        }

        /// <summary>
        /// Sets IP address, subnet mask and default gateway
        /// </summary>
        /// <param name="ipConfig">Network adapter IP configuration</param>
        public void SetIp(IPConfig ipConfig)
        {
            if (isDisposed)
            {
                throw new ObjectDisposedException("NetworkAdapter");
            }
            if (string.IsNullOrWhiteSpace(ipConfig.IPAddress))
            {
                throw new ArgumentNullException("IPAddress");
            }
            if (string.IsNullOrWhiteSpace(ipConfig.SubnetMask))
            {
                throw new ArgumentNullException("SubnetMask");
            }
            if (string.IsNullOrWhiteSpace(ipConfig.DefaultGateway))
            {
                throw new ArgumentNullException("DefaultGateway");
            }
            ManagementBaseObject newIp = adapter.GetMethodParameters("EnableStatic");
            newIp["IPAddress"] = new string[] { ipConfig.IPAddress };
            newIp["SubnetMask"] = new string[] { ipConfig.SubnetMask };

            ManagementBaseObject newGateway = adapter.GetMethodParameters("SetGateways");
            newGateway["DefaultIPGateway"] = new string[] { ipConfig.DefaultGateway };
            newGateway["GatewayCostMetric"] = new int[] { 1 };

            ManagementBaseObject setIp = adapter.InvokeMethod("EnableStatic", newIp, null);
            ManagementBaseObject setGateway = adapter.InvokeMethod("SetGateways", newGateway, null);
            newIp.Dispose();
            newGateway.Dispose();
            setIp.Dispose();
            setGateway.Dispose();
        }

        /// <summary>
        /// Sets IP address, subnet mask and default gateway
        /// </summary>
        /// <param name="ip">IP address</param>
        /// <param name="subnetMask">Subnet mask</param>
        /// <param name="gateway">Default gateway</param>
        public void SetIp(string ip, string subnetMask, string gateway)
        {
            if (isDisposed)
            {
                throw new ObjectDisposedException("NetworkAdapter");
            }
            if (string.IsNullOrWhiteSpace(ip))
            {
                throw new ArgumentNullException("ip");
            }
            if (string.IsNullOrWhiteSpace(subnetMask))
            {
                throw new ArgumentNullException("subnetMask");
            }
            if (string.IsNullOrWhiteSpace(gateway))
            {
                throw new ArgumentNullException("gateway");
            }
            ManagementBaseObject newIp = adapter.GetMethodParameters("EnableStatic");
            newIp["IPAddress"] = new string[] { ip };
            newIp["SubnetMask"] = new string[] { subnetMask };

            ManagementBaseObject newGateway = adapter.GetMethodParameters("SetGateways");
            newGateway["DefaultIPGateway"] = new string[] { gateway };
            newGateway["GatewayCostMetric"] = new int[] { 1 };

            ManagementBaseObject setIp = adapter.InvokeMethod("EnableStatic", newIp, null);
            ManagementBaseObject setGateway = adapter.InvokeMethod("SetGateways", newGateway, null);
            newIp.Dispose();
            newGateway.Dispose();
            setIp.Dispose();
            setGateway.Dispose();
        }
        #endregion

        #region constructors
        /// <summary>
        /// Creates new instance
        /// </summary>
        /// <param name="caption">Network adapter caption</param>
        /// <param name="ipOnly">Indicates if network adapter must support IP</param>
        public NetworkAdapter(string caption, bool ipOnly = true)
        {
            if (string.IsNullOrWhiteSpace(caption))
            {
                throw new ArgumentNullException("caption");
            }
            ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
            foreach (ManagementObject instance in mc.GetInstances())
            {
                if ((instance["Caption"] as string) == caption)
                {
                    if ((bool)instance["ipEnabled"] == false && ipOnly)
                    {
                        throw new ArgumentException("Requested network adapter does not allow IP configuration");
                    }
                    adapter = instance;
                    break;
                }
            }
            if (adapter == null)
            {
                throw new ArgumentException("Cannot find network adapter with given caption");
            }
            mc.Dispose();
        }
        #endregion

        #region static methods
        /// <summary>
        /// Creates NetworkAdapter instance from setting ID
        /// </summary>
        /// <param name="settingID">Network adapter setting ID</param>
        /// <param name="ipOnly">Indicates if network adapter must support IP</param>
        /// <returns></returns>
        public static NetworkAdapter CreateFromSettingID(string settingID, bool ipOnly = true)
        {
            string caption = string.Empty;
            if (string.IsNullOrWhiteSpace(settingID))
            {
                throw new ArgumentNullException("settingID");
            }
            ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
            foreach (ManagementObject instance in mc.GetInstances())
            {
                if ((instance["SettingID"] as string) == settingID)
                {
                    if ((bool)instance["IPEnabled"] == false && ipOnly)
                    {
                        throw new ArgumentException("Requested network adapter does not allow IP configuration");
                    }
                    caption = instance["Caption"] as string;
                    break;
                }
            }
            if (string.IsNullOrWhiteSpace(caption))
            {
                throw new ArgumentException("Cannot find network adapter with given settingID");
            }
            mc.Dispose();
            return new NetworkAdapter(caption);
        }
        #endregion

        #region IDisposable
        /// <summary>
        /// Disposes
        /// </summary>
        public void Dispose()
        {
            if (isDisposed == false)
            {
                if (adapter != null)
                {
                    adapter.Dispose();
                }
                isDisposed = true;
            }
        }
        #endregion
    }
}
